# -*- coding: utf-8 -*-
require_relative "./_helper"

class TestMyEnumerable < Minitest::Test

  def test_count_returns_total_times_block_evaluates_to_true
    ary = ArrayStandIn.new []
    assert_equal 0, ary.count { true }

    ary = ArrayStandIn.new %w[a b a c a d a e a f a]
    assert_equal 6, ary.count { |char| char == "a" }

    hsh = HashStandIn.new key1: :value, key2: :value, key3: :other
    assert_equal 2, hsh.count { |_, v| v == :value }
  end

  def test_count_returns_total_number_elements_when_no_block_is_given
    skip
    ary = ArrayStandIn.new []
    assert_equal 0, ary.count

    ary = ArrayStandIn.new "a"
    assert_equal 1, ary.count

    hsh = HashStandIn.new key1: "Hello", key2: "world", key3: "!"
    assert_equal 3, hsh.count
  end

  def test_find_returns_the_first_element_which_makes_block_evaluate_to_true
    skip
    ary = ArrayStandIn.new []
    assert_nil ary.find { true }

    ary = ArrayStandIn.new %w[a bcd a xyz]
    assert_equal "a", ary.find { true }
    assert_equal "bcd", ary.find { |str| str.length == 3 }

    hsh = HashStandIn.new key1: "Hello", key2: "World", key3: "!"
    assert_nil hsh.find { false }
    assert_equal [:key3, "!"], hsh.find { |k, v| v == "!" }
  end

  def test_inject_aggregates_elements_as_evaluated_by_block_into_given_object
    skip
    ary = ArrayStandIn.new []
    assert_equal 0, ary.inject(0) { |sum, num| sum + num }

    ary = ArrayStandIn.new 5, 8
    assert_equal 13, ary.inject(0) { |sum, num| sum + num }
    assert_equal 14, ary.inject(1) { |sum, num| sum + num }

    hsh = HashStandIn.new key1: "Hello", key2: "World", key3: "!"
    assert_equal "HelloWorld!", hsh.inject("") { |str, pair| str + pair[1] }
  end

  def test_inject_takes_a_symbolized_method_for_Arrays_only
    skip
    ary = ArrayStandIn.new []
    assert_equal 0, ary.inject(0, :+)

    ary = ArrayStandIn.new 5, 8
    assert_equal 13, ary.inject(0, :+)

    hsh = HashStandIn.new key1: 5, key2: 3
    e = assert_raises TypeError do
      hsh.inject(0, :+)
    end
    assert_equal "Array can't be coerced into Fixnum", e.message
  end

  def test_inject_takes_symbolized_method_over_block
    skip
    ary = ArrayStandIn.new 5, 8
    assert_equal 14, ary.inject(1, :+) { |product, num| product * num }
  end

  def test_each_with_object_returns_object_with_elements_as_evaluated_by_block
    skip
    ary = ArrayStandIn.new []
    assert_equal "", ary.each_with_object(""){ }

    ary = ArrayStandIn.new %w[a b]
    assert_equal "ab", ary.each_with_object(""){ |char, string| string << char }

    hsh = HashStandIn.new key1: "Hello", key2: "World", key3: "!"
    expected = [:key1, :key2, :key3]
    assert_equal expected, hsh.each_with_object([]) { |pair, a| a << pair[0] }
  end

  def test_to_a_returns_an_array_of_the_items_iterated_over
    skip
    ary = ArrayStandIn.new 5, 8
    assert_equal [5, 8], ary.to_a

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal [[:a, 1], [:b, 2]], hsh.to_a
  end

  def test_find_all_returns_all_the_items_where_the_block_evaluates_to_true
    skip
    ary = ArrayStandIn.new []
    assert_equal [], ary.find_all { true }
    assert_equal [], ary.find_all { false }

    ary = ArrayStandIn.new 1, 2, 1, 3, 2, 6
    assert_equal [1, 2, 1, 3, 2, 6], ary.find_all { true }
    assert_equal [], ary.find_all { false }
    assert_equal [1, 1, 3], ary.find_all { |i| i.odd? }

    hsh = HashStandIn.new a: 1, b: 2, c: 1, d: 3, e: 2, f: 6
    assert_equal [[:b, 2], [:e, 2], [:f, 6]], hsh.find_all { |_, v| v.even? }
  end

  def test_map_returns_all_elements_as_affected_by_block
    skip
    ary = ArrayStandIn.new []
    assert_equal [], ary.map { 1 }

    ary = ArrayStandIn.new %w[a b]
    assert_equal [1, 1], ary.map { 1 }

    hsh = HashStandIn.new a: 1, b: 2, c: 1, d: 3, e: 2, f: 6
    assert_equal %i[A B C D E F], hsh.map { |k, _| k.upcase }
  end

  def test_group_by_returns_all_elements_grouped_according_to_block_return_value
    skip
    ary = ArrayStandIn.new(*1..5)
    expected = { true=>[1,3,5], false=>[2,4] }
    assert_equal expected, ary.group_by { |n| n.odd? }

    hsh = HashStandIn.new k1: "abc", k2: "lol", k3: "lkjs"
    expected = {4=>[[:k3, "lkjs"]], 3=>[[:k1, "abc"], [:k2, "lol"]] }
    assert_equal expected, hsh.group_by { |_, v| v.length }
  end

  def test_take_returns_the_first_N_items
    skip
    ary = ArrayStandIn.new []
    assert_equal [], ary.take(1)

    ary = ArrayStandIn.new 1
    assert_equal [], ary.take(0)

    ary = ArrayStandIn.new 1
    assert_equal [1], ary.take(1)

    ary = ArrayStandIn.new 2, 1, 3
    assert_equal [2], ary.take(1)

    hsh = HashStandIn.new  k1: "abc", k2: "lol", k3: "lkjs"
    assert_equal [[:k1, "abc"], [:k2, "lol"]], hsh.take(2)
  end

  def test_drop_returns_all_but_the_first_N_elements
    skip
    ary = ArrayStandIn.new []
    assert_equal [], ary.drop(1)

    ary = ArrayStandIn.new 1
    assert_equal [1], ary.drop(0)

    ary = ArrayStandIn.new 1
    assert_equal [], ary.drop(1)

    ary = ArrayStandIn.new 1, 2
    assert_equal [2], ary.drop(1)

    hsh = HashStandIn.new  k1: "abc", k2: "lol", k3: "lkjs"
    assert_equal [[:k3, "lkjs"]], hsh.drop(2)
  end

  def test_each_with_index_adds_index_to_all_elements_as_they_are_passed_to_block
    skip
    seen = []
    ary = ArrayStandIn.new []
    ary.each_with_index { |item, idx| seen << idx }
    assert_equal [], seen

    index_n_value = []
    ary = ArrayStandIn.new %w[a b c]
    ary.each_with_index { |item, idx| index_n_value << [idx, item] }
    assert_equal [[0, "a"], [1, "b"], [2, "c"]], index_n_value

    index_n_pair = []
    hsh = HashStandIn.new  k1: "abc", k2: "lol", k3: "lkjs"
    hsh.each_with_index { |item, idx| index_n_pair << [ idx, item ] }
    expected = [[0, [:k1, "abc"]], [1, [:k2, "lol"]], [2, [:k3, "lkjs"]]]
    assert_equal expected, index_n_pair
  end

  def test_first_returns_the_first_element
    skip
    ary = ArrayStandIn.new []
    assert_nil ary.first

    ary = ArrayStandIn.new 1
    assert_equal 1, ary.first

    ary = ArrayStandIn.new 2, 1
    assert_equal 2, ary.first

    hsh = HashStandIn.new  k1: "abc", k2: "lol", k3: "lkjs"
    assert_equal [:k1, "abc"], hsh.first
  end

  def test_first_takes_the_first_N_items_when_N_given
    skip
    ary = ArrayStandIn.new 1, 2, 3
    assert_equal [1, 2], ary.first(2)

    hsh = HashStandIn.new  k1: "abc", k2: "lol", k3: "lkjs"
    assert_equal [[:k1, "abc"], [:k2, "lol"]], hsh.first(2)
  end

  def test_all_returns_true_when_block_evaluates_to_true_for_each_element
    skip
    ary = ArrayStandIn.new []
    assert_equal true, ary.all? { |n| n == true }

    ary = ArrayStandIn.new 1, 2
    assert_equal true, ary.all? { |n| n < 3 }

    hsh = HashStandIn.new {}
    assert_equal true, hsh.all? { |n| n == false }

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal true, hsh.all? { |_,v| v < 3 }
  end

  def test_all_returns_false_when_block_evaluates_to_false_for_each_element
    skip
    ary = ArrayStandIn.new 1, 2
    assert_equal false, ary.all? { |n| n == 2 }

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal false, hsh.all? { |k, _| k == :a }
  end

  def test_none_returns_true_when_block_evaluates_to_false_for_each_element
    skip
    ary = ArrayStandIn.new []
    assert_equal true, ary.none? { true }

    ary = ArrayStandIn.new 1
    assert_equal true, ary.none? { |n| n == 2 }

    ary = ArrayStandIn.new 1, 2
    assert_equal true, ary.none? { |n| n < 0 }

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal true, hsh.none? { |_,v| v > 3 }
  end

  def test_none_returns_false_when_block_evaluates_to_true_for_at_least_one_element
    skip
    ary = ArrayStandIn.new []
    assert_equal true, ary.none? { false }

    ary = ArrayStandIn.new 1
    assert_equal false, ary.none? { 1 }
    assert_equal false, ary.none? { |n| n == 1 }

    ary = ArrayStandIn.new 1, 2
    assert_equal false, ary.none? { |n| n == 2 }

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal false, hsh.none? { |k, _| k == :a }
  end

  def test_any_returns_true_if_the_block_returns_true_for_any_element
    skip
    ary = ArrayStandIn.new 1
    assert_equal true, ary.any? { 1 }
    assert_equal true, ary.any? { |n| n == 1 }

    ary = ArrayStandIn.new 1, 2
    assert_equal true, ary.any? { |n| n == 2 }

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal true, hsh.any? { |k, _| k == :a }
  end

  def test_any_returns_false_if_the_block_returns_false_for_all_elements
    skip
    ary = ArrayStandIn.new []
    assert_equal false, ary.any? { true }
    assert_equal false, ary.any? { false }

    ary = ArrayStandIn.new 1
    assert_equal false, ary.any? { |n| n == 2 }

    ary = ArrayStandIn.new 1, 2
    assert_equal false, ary.any? { |n| n < 0 }

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal false, hsh.any? { |_,v| v > 3 }
  end

  def test_min_by_returns_the_smallest_element_according_to_blocks_evaluation
    skip
    ary = ArrayStandIn.new []
    assert_nil ary.min_by { |s| s }

    ary = ArrayStandIn.new %w[abc z]
    assert_equal "z", ary.min_by { |s| s.length }
    assert_equal "abc", ary.min_by { |s| s }

    ary = ArrayStandIn.new nil, false
    assert_equal false, ary.min_by { |falsy| falsy.nil? ? 1 : -1 }

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal [:a, 1], hsh.min_by { |_, v| v }
  end

  def test_min_by_returns_the_first_element_when_multiple_elements_with_same_value
    skip
    ary = ArrayStandIn.new %w[a b]
    assert_equal "a", ary.min_by { 1 }

    ary = ArrayStandIn.new %w[b a]
    assert_equal "b", ary.min_by { 1 }

    hsh = HashStandIn.new b: 1, a: 1
    assert_equal [:b, 1], hsh.min_by { 1 }
  end

  def test_max_by_returns_the_largest_element_according_to_blocks_evaluation
    skip
    ary = ArrayStandIn.new []
    assert_nil ary.max_by { |s| s }

    ary = ArrayStandIn.new %w[z abc]
    assert_equal "z", ary.max_by { |s| s }

    ary = ArrayStandIn.new %w[abc z]
    assert_equal "abc", ary.max_by { |s| s.length }
    assert_equal "z", ary.max_by { |s| s }

    ary = ArrayStandIn.new nil, false
    assert_equal false, ary.max_by { |falsy| falsy.nil? ? -1 : 1 }

    hsh = HashStandIn.new a: 1, b: 2
    assert_equal [:b, 2], hsh.max_by { |_, v| v }
  end

  def test_max_by_returns_the_first_element_when_multiple_results_with_same_value
    skip
    ary = ArrayStandIn.new %w[a b]
    assert_equal "a", ary.max_by { 1 }

    ary = ArrayStandIn.new %w[b a]
    assert_equal "b", ary.max_by { 1 }

    hsh = HashStandIn.new b: 1, a: 1
    assert_equal [:b, 1], hsh.max_by { 1 }
  end

  def test_include_returns_true_when_element_exists
    skip
    ary = ArrayStandIn.new 1
    assert_equal true, ary.include?(1)

    ary = ArrayStandIn.new 2, 1
    assert_equal true, ary.include?(1)

    hsh = HashStandIn.new  k1: "abc", k2: "lol", k3: "lkjs"
    assert_equal true, hsh.include?([:k1, "abc"])
  end

  def test_include_returns_false_when_no_such_element_existst
    skip
    ary = ArrayStandIn.new []
    assert_equal false, ary.include?(1)

    ary = ArrayStandIn.new 1
    assert_equal false, ary.include?(3)

    ary = ArrayStandIn.new 2, 1
    assert_equal false, ary.include?(3)

    hsh = HashStandIn.new  k1: "abc", k2: "lol", k3: "lkjs"
    assert_equal false, hsh.include?([:k4, "abc"])
  end

  def test_reject_returns_all_elements_for_which_block_evaluates_to_false
    skip
    keys = (:a..:j).to_a
    values = (1..10).to_a
    some_hash = keys.zip(values).to_h

    ary = ArrayStandIn.new values
    assert_equal [1,3,5,7,9], ary.reject { |i| i % 2 == 0 }
    assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ary.reject { |i| false }

    hsh = HashStandIn.new some_hash
    expected = [[:a, 1], [:c, 3], [:e, 5], [:g, 7], [:i, 9]]
    assert_equal expected, hsh.reject { |_,v| v % 2 == 0 }
    assert_equal [], hsh.reject { |k, v| true }
  end
end
